﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;

namespace Ue.Infrastructure.Caching
{
    public class Cache<TKey,TResult> : ICache<TKey,TResult>
    {
        private readonly ICacheContextAccessor cacheContextAccessor;
        private readonly ConcurrentDictionary<TKey, CacheEntry> entries;

        public Cache(ICacheContextAccessor cacheContentAccessor)
        {
            this.cacheContextAccessor = cacheContentAccessor;
            this.entries = new ConcurrentDictionary<TKey, CacheEntry>();
        }

        #region ICache<TKey,TResult> 成员

        public TResult Get(TKey key, Func<AcquireContext<TKey>, TResult> acquire)
        {
            var entity = this.entries.AddOrUpdate(
                key,
                k => this.AddEntry(k,acquire),
                (k, currentEntry) => this.UpdateEntry(currentEntry,k,acquire));

            return entity.Result;
        }

        #endregion

        private CacheEntry AddEntry(TKey key,Func<AcquireContext<TKey>, TResult> acquire)
        {
            var entity = this.CreateEntry(key, acquire);
            this.PropagateTokens(entity);
            return entity;
        }

        private CacheEntry UpdateEntry(CacheEntry currentEntry, TKey k, Func<AcquireContext<TKey>, TResult> acquire)
        {
            var entry = (currentEntry.Tokens.Any(t => !t.IsCurrent)) ? this.CreateEntry(k, acquire) : currentEntry;
            PropagateTokens(entry);
            return entry;
        }

        private CacheEntry CreateEntry(TKey key, Func<AcquireContext<TKey>, TResult> acquire)
        {
            var entry = new CacheEntry();
            var context = new AcquireContext<TKey>(key, entry.AddToken);


            //push context 和 Pop context 以及 PropagateTokens 形成父子级Token关联
            //如：ICache.Get(key,context=>{
            //          var sub=ICache.Get(subKey,subContext=>{
            //              subContext.Monitor(new Token());
            //              return subEntity;
            //          });
            //          
            //          context.Monitor(new Token());
            //
            //          return entity;      
            //    });
            IAcquireContext parentContext = null;
            try
            {
                // Push context
                parentContext = this.cacheContextAccessor.Current;
                this.cacheContextAccessor.Current = context;

                entry.Result = acquire(context);
            }
            finally
            {
                // Pop context
                this.cacheContextAccessor.Current = parentContext;
            }

            entry.CompactTokens();

            return entry;
        }

        private void PropagateTokens(CacheEntry entry)
        {
            // Bubble up volatile tokens to parent context
            if (this.cacheContextAccessor.Current != null)
            {
                foreach (var token in entry.Tokens)
                {
                    this.cacheContextAccessor.Current.Monitor(token);
                }
            }
        }

        private class CacheEntry
        {
            private IList<IVolatileToken> tokens;
            public TResult Result { get; set; }

            public IEnumerable<IVolatileToken> Tokens
            {
                get { return this.tokens ?? Enumerable.Empty<IVolatileToken>(); }
            }

            public void AddToken(IVolatileToken volatileToken)
            {
                if (this.tokens == null)
                {
                    this.tokens = new List<IVolatileToken>();
                }

                tokens.Add(volatileToken);
            }

            public void CompactTokens()
            {
                if (tokens != null)
                {
                    tokens = tokens.Distinct().ToArray();
                }
            }
        }
    }
}
